package com.red.wood.common.ip.ipipnet;

import com.alibaba.fastjson.JSONObject;
import sun.net.util.IPAddressUtil;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;

/**
 * https://www.ipip.net/download.html
 */
public class Reader {

    private int fileSize;
    private int nodeCount;

    private MetaData meta;
    private byte[] data;

    private int v4offset;

    public Reader(String name) throws IOException {

        File file = new File(name);
        this.fileSize = new Long(file.length()).intValue();

        Path path = Paths.get(name);
        this.data = Files.readAllBytes(path);

        long metaLength = bytesToLong(
            this.data[0],
            this.data[1],
            this.data[2],
            this.data[3]
        );

        byte[] metaBytes = Arrays.copyOfRange(this.data, 4, Long.valueOf(metaLength).intValue() + 4);

        MetaData meta = JSONObject.parseObject(new String(metaBytes), MetaData.class);

        this.nodeCount = meta.nodeCount;

        this.meta = meta;

        this.data = Arrays.copyOfRange(this.data, Long.valueOf(metaLength).intValue() + 4, this.fileSize);

        if (this.v4offset == 0) {
            int node = 0;
            for (int i = 0; i < 96 && node < this.nodeCount; i++) {
                if (i >= 80) {
                    node = this.readNode(node, 1);
                } else {
                    node = this.readNode(node, 0);
                }
            }

            this.v4offset = node;
        }
    }

    @SuppressWarnings("restriction")
	public String[] find(String addr, String language){
    	try {
    		 int off;
    	        try {
    	            off = this.meta.Languages.get(language);
    	        } catch (NullPointerException e) {
    	            return null;
    	        }

    	        byte[] ipv;

    	        if (addr.indexOf(":") > 0) {
    	            ipv = IPAddressUtil.textToNumericFormatV6(addr);
    	            if (ipv == null) {
    	                throw new IPFormatException("ipv6 format error");
    	            }
    	            if ((this.meta.IPVersion & 0x02) != 0x02){
    	                throw new IPFormatException("no support ipv6");
    	            }

    	        } else if (addr.indexOf(".") > 0) {
    	            ipv = IPAddressUtil.textToNumericFormatV4(addr);
    	            if (ipv == null) {
    	                throw new IPFormatException("ipv4 format error");
    	            }
    	            if ((this.meta.IPVersion & 0x01) != 0x01){
    	                throw new IPFormatException("no support ipv4");
    	            }
    	        } else {
    	            throw new IPFormatException("ip format error");
    	        }

    	        final int node = this.findNode(ipv);
    	        final String data = this.resolve(node);

    	        return Arrays.copyOfRange(data.split("\t", this.meta.Fields.length * this.meta.Languages.size()), off, off+this.meta.Fields.length);
    	}catch(Exception e) {
    		return new String[] {"","",""};
    	}
    }

    private int findNode(byte[] binary) throws InvalidDatabaseException {

        int node = 0;

        final int bit = binary.length * 8;

        if (bit == 32) {
            node = this.v4offset;
        }

        for (int i = 0; i < bit; i++) {
            if (node > this.nodeCount) {
                break;
            }

            node = this.readNode(node, 1 & ((0xFF & binary[i / 8]) >> 7 - (i % 8)));
        }

        if (node == this.nodeCount) {
            return 0;
        } else if (node > this.nodeCount) {
            return node;
        }

        throw new InvalidDatabaseException("xx");
    }

    private String resolve(int node) throws InvalidDatabaseException {
        final int resoloved = node - this.nodeCount + this.nodeCount * 8;
        if (resoloved >= this.fileSize) {
            throw new InvalidDatabaseException("database resolve error");
        }

        byte b = 0;
        int size = Long.valueOf(bytesToLong(
            b,
            b,
            this.data[resoloved],
            this.data[resoloved+1]
        )).intValue();

        if (this.data.length < (resoloved + 2 + size)) {
            throw new InvalidDatabaseException("database resolve error");
        }

        return new String(this.data, resoloved + 2, size);
    }

    private int readNode(int node, int index) {
        int off = node * 8 + index * 4;

        return Long.valueOf(bytesToLong(
            this.data[off],
            this.data[off+1],
            this.data[off+2],
            this.data[off+3]
        )).intValue();
    }

    private static long bytesToLong(byte a, byte b, byte c, byte d) {
        return int2long((((a & 0xff) << 24) | ((b & 0xff) << 16) | ((c & 0xff) << 8) | (d & 0xff)));
    }

    private static long int2long(int i) {
        long l = i & 0x7fffffffL;
        if (i < 0) {
            l |= 0x080000000L;
        }
        return l;
    }

    public int getBuildUTCTime() {
        return this.meta.Build;
    }

    public String[] getSupportFields() {
        return this.meta.Fields;
    }
}
